package org.folio.rest.impl.other.reserve_station;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;

import io.vertx.core.Context;
import io.vertx.core.Vertx;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;

import org.folio.rest.impl.other.PartyUtil;

import org.folio.rest.impl.other.reserve_station.model.ReserveDelayed;
import org.folio.rest.jaxrs.model.Attend;
import org.folio.rest.jaxrs.model.Reserve;
import org.folio.rest.jaxrs.model.WebReserveStatus;

import org.folio.rest.persist.PostgresClient;
import org.folio.rest.persist.cql.CQLWrapper;

import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReserveQueueService {
    private final static Logger logger = LoggerFactory.getLogger("modparty");
    private ReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock r = rwl.readLock();
    private final Lock w = rwl.writeLock();
    private ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    private BlockingQueue<Reserve> queueServe = null;
    private ReserveDelayed reserveDelayed;
    private CopyOnWriteArraySet<Reserve> basket = new CopyOnWriteArraySet<>( );
    private static ConcurrentHashMap<String, String> attendKeyMap =
            new ConcurrentHashMap<>();
    private Vertx vertx;
    private String tenant;
    private Map<String, String> okapiHeaders;
    private Context vertxContext;
    public ReserveQueueService(ReserveDelayed reserveDelayed) {
        this.reserveDelayed = reserveDelayed;
        this.queueServe = new LinkedBlockingDeque();
    }
    public ReserveQueueService(ReserveDelayed reserveDelayed,Vertx vertx,String tenant) {
        this.reserveDelayed = reserveDelayed;
        this.queueServe = new LinkedBlockingDeque();
        this.vertx = vertx;
        this.tenant = tenant;
        CQLWrapper cql = PartyUtil.CQLCreate(" partyId == *" + reserveDelayed.getPartyId() + "* ", -1, -1, "reserve");
        PostgresClient.getInstance(vertx,tenant).get("reserve",Reserve.class,new String[] {"*"},cql,false,reply->{
            if (reply.succeeded()){
                reply.result().getResults().forEach(a->{
                    this.basket.add(a);
                });
                logger.info("添加了"+this.basket.size()+"个已报名记录");
            }
        });
    }

    public void startQueueThread() {

        cachedThreadPool.execute(() -> {

            while (true) {

                try {

                    if (basket.size() < reserveDelayed.getReserveAmount() && queueServe.size() > 0 ){
                        Reserve reserve = queueServe.take();
                        basket.add(reserve);
                        saveData(reserve);
                        Thread.sleep(1000);
                    }

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }
            }
        });

    }

    private void saveData(Reserve reserve) {
        if (reserve == null){
            return;
        }
        PostgresClient pgUtil = PostgresClient.getInstance(vertx, tenant);
        pgUtil.save("reserve",reserve.getId(),reserve,reply->{
            if (reply.succeeded()){
                Attend attend = createAttendRecord(reserve);
                pgUtil.save("attend",attend.getId(),attend,attendReply->{
                      if (attendReply.succeeded()){
                           attendKeyMap.put(attend.getPartyId(),attend.getId());
                          logger.info("用户报名成功!");
                      }else{
                          logger.info("用户报名失败!");
                          logger.info(reply.cause().getMessage());
                      }
                });
            }else{
                logger.info("用户报名失败!");
                logger.info(reply.cause().getMessage());
            }
        });
    }
    private void removeData(Reserve reserve) {
        if (reserve == null){
            return;
        }
        PostgresClient pgUtil = PostgresClient.getInstance(vertx, tenant);
        pgUtil.delete("reserve",reserve.getId(),reply->{
            if (reply.succeeded()){
                String attendKey = attendKeyMap.get(reserve.getPartyId());
                if (!StrUtil.isBlankOrUndefined(attendKey)){
                    pgUtil.delete("attend",attendKey,replyDel->{
                        if (replyDel.succeeded()){
                            //通知用户

                            logger.info("用户取消报名成功!");
                        }else{
                            logger.info("用户取消报名失败!");
                            logger.info(reply.cause().getMessage());
                        }
                    });
                }
            }
        });

    }
    private Attend createAttendRecord(Reserve reserve) {
        DateTime date = DateUtil.date();
        Attend attend = new Attend();
        attend.setAttendDate(date.toString());
        attend.setMetadata(reserve.getMetadata());
        attend.setReserveId(reserve.getId());
        attend.setState(0);
        attend.setReaderReserveGroup(reserve.getReaderReserveGroup());
        attend.setPropertyName(reserve.getPropertyName());
        attend.setPartyId(reserve.getPartyId());
        attend.setPartyStartDate(reserve.getPartyStartDate());
        attend.setPartyName(reserve.getPartyName());
        attend.setIsDel(0);
        attend.setId(UUID.randomUUID().toString());
        attend.setOperator(reserve.getOperator());
        attend.setAttendState(5);
        attend.setReserveDate(reserve.getReserveDate());
        return attend;
    }
    //用户进入报名队列
    public void addReserve(Reserve reserve) {
        try {
           this.queueServe.put(reserve);
        } catch (IllegalStateException | InterruptedException e) {
           e.printStackTrace();
        }
    }
    //获取排队中的长度
    public Integer getWaitReserveLength(){
        return this.queueServe.size();
    }
    //获得已报名的队伍长度
    public Integer getAlreadyReserveLength(){
        return this.basket.size();
    }
    //用户取消报名
    public Boolean cancelReserve(String barcode){
        if (StrUtil.isBlankOrUndefined(barcode)){
            return false;
        }
        Optional<Reserve> already = this.basket.stream().filter(a -> a.getReaderReserveGroup().get(0).getBarcode().equals(barcode)).findAny();
        Optional<Reserve> queue = this.queueServe.stream().filter(a -> a.getReaderReserveGroup().get(0).getBarcode().equals(barcode)).findAny();
        if (!already.isPresent() && !queue.isPresent()){
            return false;
        }
        w.lock();
        try{
            this.basket.removeIf(a->a.getReaderReserveGroup().get(0).getBarcode().equals(barcode));
            this.queueServe.removeIf(a->a.getReaderReserveGroup().get(0).getBarcode().equals(barcode));
            if (already.isPresent()){
                removeData(already.get());
            }
            return true;
        }catch (Exception e){
            return false;
        }finally {
            w.unlock();
        }
    }

    /**
     * 获得报名状态 isRserve false 未报名
     * @param barcode
     * @return
     */
    public WebReserveStatus readerReserveStatus(String barcode){
        WebReserveStatus webReserveStatus = new WebReserveStatus();
        if (StrUtil.isBlankOrUndefined(barcode)){
                return null;
        }
        Optional<Reserve> already = this.basket.stream().filter(a -> a.getReaderReserveGroup().get(0).getBarcode().equals(barcode)).findAny();
        Optional<Reserve> queue = this.queueServe.stream().filter(a -> a.getReaderReserveGroup().get(0).getBarcode().equals(barcode)).findAny();

        if (already.isPresent()){
            webReserveStatus.setIsReserve(true);
            webReserveStatus.setRemaining(0);
            return webReserveStatus;
        }
        if (queue.isPresent() && !already.isPresent()){
            webReserveStatus.setIsReserve(false);
            int index = 1;
            for ( Reserve reserve:
                 this.queueServe) {
                if (reserve.getReaderReserveGroup().get(0).getBarcode().equals(barcode)){
                    break;
                }
                index++;

            }
            index = this.basket.size()+index;
            webReserveStatus.setRemaining(index);
            return webReserveStatus;
        }
        webReserveStatus.setIsReserve(false);
        webReserveStatus.setRemaining(0);
        return webReserveStatus;

    }


    public CopyOnWriteArraySet<Reserve> getBasket() {
        return basket;
    }

    public Optional<Reserve> getReaderQueue(String barcode){
        return queueServe.stream().filter(a->a.getReaderReserveGroup().get(0).getBarcode().equals(barcode)).findAny();
    }
}
